package bancosys.tec.rtm.impl.diagnostician;

import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryUsage;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import bancosys.tec.rtm.diagnostician.AbstractDiagnostician;
import bancosys.tec.rtm.diagnostician.DiagnosisBean;
import bancosys.tec.utils.reflection.ReflectionUtils;

/**
 * Diagnostician that probes the GC(s) for information. Special cases sun JDK's GarbageCollector - which contains the last GC run info
 * 
 * @author takeshi
 */
public class GarbageCollectorDiagnostician extends AbstractDiagnostician {

    /**
     * {@inheritDoc}
     */
    public String getName() {
        return "Garbage Collector Activity Diagnosticia";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean fillDiagnosisBean(DiagnosisBean bean) throws Exception {
        List<GarbageCollectorMXBean> list = ManagementFactory.getGarbageCollectorMXBeans();
        for (GarbageCollectorMXBean garbageCollectorMXBean : list) {
            bean.addDescription("GC: " + garbageCollectorMXBean.getName());
            bean.addDescription("Collection count: " + garbageCollectorMXBean.getCollectionCount());
            bean.addDescription("Total collection time: " + garbageCollectorMXBean.getCollectionTime());
            bean.addDescription("Memory regions: " + Arrays.toString(garbageCollectorMXBean.getMemoryPoolNames()));
            this.extractLastGCInfo(bean, garbageCollectorMXBean);
        }
        return true;
    }

    /**
     * Extracts the last GC Information if the given MBean implements the sun's {@link GarbageCollectorMXBean} interface. Ignores any
     * exception (ie LinkageError, etc)
     * 
     * @param bean the {@link DiagnosisBean}
     * @param garbageCollectorMXBean the {@link GarbageCollectorMXBean}
     */
    @SuppressWarnings("unchecked")
    private void extractLastGCInfo(DiagnosisBean bean, GarbageCollectorMXBean garbageCollectorMXBean) {
        try {
            Class.forName("com.sun.management.GarbageCollectorMXBean");

            Object lastGcInfo = ReflectionUtils.invoke(garbageCollectorMXBean, "getLastGcInfo");

            long lastStartTime = (Long) ReflectionUtils.invoke(lastGcInfo, "getStartTime");
            long lastDuration = (Long) ReflectionUtils.invoke(lastGcInfo, "getDuration");

            Date start = new Date(lastStartTime + ManagementFactory.getRuntimeMXBean().getStartTime());
            DateFormat df = DateFormat.getDateTimeInstance(SimpleDateFormat.FULL, SimpleDateFormat.FULL, Locale.US);
            bean.addDescription("Last GC run information");
            bean.addDescription("Started at: " + df.format(start));
            bean.addDescription("Duration: " + lastDuration + "ms");

            Map<String, MemoryUsage> mapAfter = (Map<String, MemoryUsage>) ReflectionUtils.invoke(lastGcInfo, "getMemoryUsageAfterGc");
            Map<String, MemoryUsage> mapBefore = (Map<String, MemoryUsage>) ReflectionUtils.invoke(lastGcInfo, "getMemoryUsageBeforeGc");

            for (String name : mapAfter.keySet()) {
                MemoryUsage before = mapBefore.get(name);
                MemoryUsage after = mapAfter.get(name);
                if (before != null && after != null) {
                    bean.addDescription("Memory region: " + name);
                    bean.addDescription("Max Memory: " + before.getMax());
                    bean.addDescription("Usage (before) Memory: " + before.getUsed());
                    bean.addDescription("Usage (after) Memory: " + after.getUsed());
                }
            }
        } catch (Throwable t) {
            // ignore all exceptions
            bean.addDescription("Cannot get last GC run information: " + t.getMessage());
        }
    }
}
